#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cstdlib>
// 网络套接字4剑客
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/wait.h>
#include <signal.h>
#include <functional>
#include "log.hpp"

#include "protocol.hpp"

using namespace std;
namespace server
{

    enum
    {
        Usage_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        OPEN_ERR
    };
    // typedef function<void(int)> func_t;//可以将handerEntery写成回调函数放到其他地方
    const static uint16_t gport = 8081;
    const static int listen_num = 10;

    typedef function<bool(const Request &req, Response &resp)> func_t; // req输入型参数，resp输出型参数
    //using func_t = function<bool(const Request &req, Response &resp)>;
    void handerEntery(int sock, func_t func)                           // 业务逻辑,，解耦
    {
        string inbuffer;
        while (true)
        {
            ////1、读取————【IO】
            //  1-1、我可能读到0.5/1.5/...多种请求。那怎么保证读取到的消息，是【一个完整】的请求？
            ////[tcp面向字节流的，所以要方法就是：明确报文和报文边界——3中方法，最后的知识点里面有说明]////
            string req_text, req_str;
            // 1-2、我们保证，到这里，req_text里面一定是一个完整的请求："content_len"\r\n"x op y"\r\n
            if (!recvPackage(sock, inbuffer, &req_text)) // 如果recvRequest失败，直接return
                return;
            cout << "带长度的请求：\n"
                 << req_text << endl;
            if (!deLength(req_text, &req_str)) // 如果deLength失败，直接return
                return;
            cout << "不带长度的请求：\n"
                 << req_str << endl;

            ////2、反序列化————【协议】
            // 2-1、得到一个结构化的【请求】对象
            Request req;
            if (!req.deserialize(req_str)) // 反序列化已经将req里面的值填充了
                return;
            ////3、业务逻辑处理————【业务逻辑】
            // 3-1、得到一个结构化的【响应】对象
            Response resp;   //--------------所以，我业务逻辑处理和你协议，IO没有半毛钱关系！解耦出去！！！
            func(req, resp); // req的处理结果全部放到了resp里面

            ////4、对响应得到的结构化对象进行序列化————【协议】（就是业务处理完之后，我们要将数据发送回去，要进行序列化）
            // 4-1、得到一个【字节流】的“字符串”
            string resp_str;
            resp.serialize(&resp_str);
            cout << "计算完成，序列化响应" << resp_str << endl;
            ////5、最后发送响应————【IO】（将我们要返回发送的数据序列化之后，进行发送）
            // 5-1、构建成为一个完整的报文
            std::string send_string = enLength(resp_str); // 这才是要发送的信息
            cout << "构建完成完整的响应\n" << send_string << endl;

            // 我们以前读写是有问题的，今天解决读的问题，但是写的问题还在，我们以后解决
            send(sock, send_string.c_str(), send_string.size(), 0);
        }
    }
    class calserver
    {

    public:
        calserver(const uint16_t &port = gport)
            : _listensock(-1), _port(port)
        {
        }
        void initserver()
        {
            // 1、创建套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0); // AF_INET表示ipv4协议;SOCK_STREAM表示面向字节流(tcp)
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success : %d", _listensock);

            // 2、bind绑定自己的网络信息
            struct sockaddr_in local; // 这个local是栈上面的，我们填了数据，但是os不知道，所以我们要交给os需要用到bind
            memset(&local, 0, sizeof(local));
            // bzero(&local,sizeof(local)); // 将每个字节全部置0
            local.sin_family = AF_INET;
            // local.sin_family = PF_INET; //AF和PF是一样的
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            ////----我们的udp上面两步执行完了就可以进行下面的收发数据操作了，但是tcp还不行

            // 我们tcp是要进行连接的——所以你客户端不能直接和我通信，要先和我建立连接
            // 3、设置socket为监听状态！监听状态让服务端一直监听，获取客户端的新连接
            if (listen(_listensock, listen_num) < 0) // 底层真实的值是listen_num+1
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start(func_t func)
        {

            while (1)
            {
                // 4、获取新连接
                struct sockaddr_in peer; // 存放客户端ip+port
                socklen_t len = sizeof(peer);
                // accept返回值也是一个套接字（文件描述符） —— 是真正tcp服务端和客户端进行通信的套接字
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept socket error , next"); // 我服务端这次连接失败了，但是不影响我下次的连接工作
                    continue;                                        // 服务端继续进行服务！
                }
                logMessage(NORMAL, "accept socket success,get new sock : %d", sock);

                // 5、这里就有了套接字sock，未来就是用这个套接字进行通信的。因为tc面向字节流，所以后续全部都是文件操作！
                // version 版本2 多进程
                pid_t id = fork();
                if (id == 0) // 子进程
                {
                    close(_listensock);
                    if (fork() > 0)
                    {
                        exit(0);
                    }
                    // serverIO(sock);
                    handerEntery(sock, func);
                    close(sock);
                    exit(0); // 终止子进程
                }
                close(sock);
                pid_t ret = waitpid(id, nullptr, 0);
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait child success");
                }
            }
        }

        ~calserver() {}

    private:
        int _listensock; // 不是用来通信，只是用来监听，获取新连接的
        uint16_t _port;
    };
}